package chen.huai.jie.spring.xmlconfig.mybatis;

import org.apache.ibatis.annotations.*;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.FetchType;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.UnknownTypeHandler;

import javax.persistence.Table;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ResultBuilder {
    private Configuration configuration;
    private MapperBuilderAssistant assistant;
    private Class<?> type;

    public ResultBuilder() {
    }

    public String buildResultMap(Configuration configuration, MapperBuilderAssistant assistant, Class<?> type, Method method, boolean isSelect) {
        this.configuration = configuration;
        this.assistant = assistant;
        this.type = type;
        String resultMapId = null;
        ResultMap resultMapAnnotation = (ResultMap) method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
            String[] resultMaps = resultMapAnnotation.value();
            StringBuilder sb = new StringBuilder();
            String[] var10 = resultMaps;
            int var11 = resultMaps.length;

            for (int var12 = 0; var12 < var11; ++var12) {
                String resultMap = var10[var12];
                if (sb.length() > 0) {
                    sb.append(",");
                }

                sb.append(resultMap);
            }

            resultMapId = sb.toString();
        } else if (isSelect) {
            resultMapId = this.parseResultMap(configuration, type, method);
        }

        return resultMapId;
    }

    private String parseResultMap(Configuration configuration, Class<?> type, Method method) {
        String resultMapId = type.getName() + "." + method.getName() + "-Inline";
        Class<?> returnType = MapperUtils.getReturnType(method, type);
        if (returnType.getAnnotation(Table.class) != null) {
            EntityPortray entityPortray = new EntityPortray(returnType);
            Map<String, String> columnMap = entityPortray.getColumnMap();
            Map<String, Class<?>> typeMap = entityPortray.getColumnTypeMap();
            Map<String, Class<? extends TypeHandler>> typeHandlers = entityPortray.getColumnTypeHandlers();
            List<ResultMapping> resultMappings = new ArrayList();
            columnMap.forEach((prop, column) -> {
                ResultMapping.Builder builder = new ResultMapping.Builder(configuration, prop, column, (Class) typeMap.get(prop));
                if (typeHandlers.containsKey(prop)) {
                    builder.typeHandler(this.resolveTypeHandler(configuration, (Class) typeMap.get(prop), (Class) typeHandlers.get(prop)));
                }

                resultMappings.add(builder.build());
            });
            org.apache.ibatis.mapping.ResultMap.Builder builder = new org.apache.ibatis.mapping.ResultMap.Builder(configuration, resultMapId, returnType, resultMappings);
            configuration.addResultMap(builder.build());
        } else {
            resultMapId = this.parseResultMap(method, returnType, type);
        }

        return resultMapId;
    }

    private String parseResultMap(Method method, Class<?> returnType, Class<?> type) {
        ConstructorArgs args = (ConstructorArgs) method.getAnnotation(ConstructorArgs.class);
        Results results = (Results) method.getAnnotation(Results.class);
        TypeDiscriminator typeDiscriminator = (TypeDiscriminator) method.getAnnotation(TypeDiscriminator.class);
        String resultMapId = this.generateResultMapName(method, type);
        this.applyResultMap(resultMapId, returnType, this.argsIf(args), this.resultsIf(results), typeDiscriminator);
        return resultMapId;
    }

    private String generateResultMapName(Method method, Class<?> type) {
        Results results = (Results) method.getAnnotation(Results.class);
        if (results != null && !results.id().isEmpty()) {
            return type.getName() + "." + results.id();
        } else {
            StringBuilder suffix = new StringBuilder();
            Class[] var5 = method.getParameterTypes();
            int var6 = var5.length;

            for (int var7 = 0; var7 < var6; ++var7) {
                Class<?> c = var5[var7];
                suffix.append("-");
                suffix.append(c.getSimpleName());
            }

            if (suffix.length() < 1) {
                suffix.append("-void");
            }

            return type.getName() + "." + method.getName() + suffix;
        }
    }

    private Result[] resultsIf(Results results) {
        return results == null ? new Result[0] : results.value();
    }

    private Arg[] argsIf(ConstructorArgs args) {
        return args == null ? new Arg[0] : args.value();
    }

    private boolean hasNestedSelect(Result result) {
        if (result.one().select().length() > 0 && result.many().select().length() > 0) {
            throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
        } else {
            return result.one().select().length() > 0 || result.many().select().length() > 0;
        }
    }

    private boolean isLazy(Result result) {
        boolean isLazy = this.configuration.isLazyLoadingEnabled();
        if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
            isLazy = result.one().fetchType() == FetchType.LAZY;
        } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
            isLazy = result.many().fetchType() == FetchType.LAZY;
        }

        return isLazy;
    }

    private String nestedSelectId(Result result) {
        String nestedSelect = result.one().select();
        if (nestedSelect.length() < 1) {
            nestedSelect = result.many().select();
        }

        if (!nestedSelect.contains(".")) {
            nestedSelect = this.type.getName() + "." + nestedSelect;
        }

        return nestedSelect;
    }

    private String nullOrEmpty(String value) {
        return value != null && value.trim().length() != 0 ? value : null;
    }

    private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results, TypeDiscriminator discriminator) {
        List<ResultMapping> resultMappings = new ArrayList();
        this.applyConstructorArgs(args, returnType, resultMappings);
        this.applyResults(results, returnType, resultMappings);
        Discriminator disc = this.applyDiscriminator(resultMapId, returnType, discriminator);
        this.assistant.addResultMap(resultMapId, returnType, (String) null, disc, resultMappings, (Boolean) null);
        this.createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
    }

    private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
        if (discriminator == null) {
            return null;
        } else {
            String column = discriminator.column();
            Class<?> javaType = discriminator.javaType() == Void.TYPE ? String.class : discriminator.javaType();
            JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
            Class<? extends TypeHandler<?>> typeHandler = discriminator.typeHandler() == UnknownTypeHandler.class ? null : (Class<? extends TypeHandler<?>>) discriminator.typeHandler();
            Case[] cases = discriminator.cases();
            Map<String, String> discriminatorMap = new HashMap();
            Case[] var10 = cases;
            int var11 = cases.length;

            for (int var12 = 0; var12 < var11; ++var12) {
                Case c = var10[var12];
                String value = c.value();
                String caseResultMapId = resultMapId + "-" + value;
                discriminatorMap.put(value, caseResultMapId);
            }

            return this.assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
        }
    }

    private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
        if (discriminator != null) {
            Case[] var4 = discriminator.cases();
            int var5 = var4.length;

            for (int var6 = 0; var6 < var5; ++var6) {
                Case c = var4[var6];
                String caseResultMapId = resultMapId + "-" + c.value();
                List<ResultMapping> resultMappings = new ArrayList();
                this.applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
                this.applyResults(c.results(), resultType, resultMappings);
                this.assistant.addResultMap(caseResultMapId, c.type(), resultMapId, (Discriminator) null, resultMappings, (Boolean) null);
            }
        }

    }

    private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
        Result[] var4 = results;
        int var5 = results.length;

        for (int var6 = 0; var6 < var5; ++var6) {
            Result result = var4[var6];
            List<ResultFlag> flags = new ArrayList();
            if (result.id()) {
                flags.add(ResultFlag.ID);
            }

            Class<? extends TypeHandler<?>> typeHandler = result.typeHandler() == UnknownTypeHandler.class ? null : (Class<? extends TypeHandler<?>>) result.typeHandler();
            ResultMapping resultMapping = this.assistant.buildResultMapping(resultType, this.nullOrEmpty(result.property()), this.nullOrEmpty(result.column()), result.javaType() == Void.TYPE ? null : result.javaType(), result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(), this.hasNestedSelect(result) ? this.nestedSelectId(result) : null, (String) null, (String) null, (String) null, typeHandler, flags, (String) null, (String) null, this.isLazy(result));
            resultMappings.add(resultMapping);
        }

    }

    private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
        Arg[] var4 = args;
        int var5 = args.length;

        for (int var6 = 0; var6 < var5; ++var6) {
            Arg arg = var4[var6];
            List<ResultFlag> flags = new ArrayList();
            flags.add(ResultFlag.CONSTRUCTOR);
            if (arg.id()) {
                flags.add(ResultFlag.ID);
            }

            Class<? extends TypeHandler<?>> typeHandler = arg.typeHandler() == UnknownTypeHandler.class ? null : (Class<? extends TypeHandler<?>>) arg.typeHandler();
            ResultMapping resultMapping = this.assistant.buildResultMapping(resultType, (String) null, this.nullOrEmpty(arg.column()), arg.javaType() == Void.TYPE ? null : arg.javaType(), arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(), this.nullOrEmpty(arg.select()), this.nullOrEmpty(arg.resultMap()), (String) null, (String) null, typeHandler, flags, (String) null, (String) null, false);
            resultMappings.add(resultMapping);
        }

    }

    private TypeHandler<?> resolveTypeHandler(Configuration configuration, Class<?> javaType, Class<? extends TypeHandler> typeHandlerType) {
        if (typeHandlerType == null) {
            return null;
        } else {
            TypeHandler<?> handler = configuration.getTypeHandlerRegistry().getMappingTypeHandler((Class<? extends TypeHandler<?>>) typeHandlerType);
            if (handler == null) {
                handler = configuration.getTypeHandlerRegistry().getInstance(javaType, typeHandlerType);
            }

            return handler;
        }
    }
}
